#include "minicon.h"
#include "miniopt.h"
#include <shellapi.h>

extern int err_ret(int errcode, const char *msg, const WCHAR *name);
extern int check_samefiles(const char *op, WCHAR *src, WCHAR *dst, bool move);

//=============================================================================

#define SDDATA_SIZE 64*1024

static char *s_psd = NULL;
static bool s_psd_ready = false;
static SECURITY_ATTRIBUTES s_sa;

static bool get_security_attr(const WCHAR *src)
{
   s_psd_ready = false;
   Win::alloc((void* &)s_psd, SDDATA_SIZE);

   WCHAR mp[4+MAX_PATH], *nt;
   if (NULL == (nt = Opt::nt_name(src, mp)))
      return false;
      
   SECURITY_INFORMATION si = DACL_SECURITY_INFORMATION;
   SECURITY_DESCRIPTOR *sd = (SECURITY_DESCRIPTOR *)s_psd;
   DWORD Needed;
   BOOL RetSec = ::GetFileSecurity(nt, si, sd, SDDATA_SIZE, &Needed);
   if (!RetSec || Needed>SDDATA_SIZE)
      return false;

   s_sa.nLength = sizeof(SECURITY_ATTRIBUTES);
   s_sa.lpSecurityDescriptor = sd;
   s_sa.bInheritHandle = FALSE;

   return (s_psd_ready = true);
}

static bool set_security_attr(const WCHAR *dst)
{
   if (!s_psd_ready)
      return false;

   WCHAR mp[4+MAX_PATH], *nt;
   if (NULL == (nt = Opt::nt_name(dst, mp)))
      return false;

   SECURITY_INFORMATION si = DACL_SECURITY_INFORMATION;
   BOOL RetSec = ::SetFileSecurity(nt,si,(PSECURITY_DESCRIPTOR)s_sa.lpSecurityDescriptor);
   return RetSec != 0;
}

//=============================================================================

static char s_confirm = 'y';

static int cp_mv(
   const char *op,
   WCHAR *src, WCHAR *dst, WIN32_FIND_DATAW *ff,
   int verbose, bool preserve, int force, bool interactive, bool update,
   bool recursive )
{
   WIN32_FIND_DATAW ff_d, *dff = NULL;
   HANDLE hFind = ::FindFirstFileW(dst, &ff_d);
   if ( INVALID_HANDLE_VALUE != hFind )
   {
      dff = &ff_d;
      ::FindClose(hFind);
   }

   if (dff)
   {
      int rc = check_samefiles(op, src, dst, op[0] == 'm');
      if (0 != rc)
         return rc;
   }
   
   if ( verbose )
   {
      Con::o(op); Con::f(L" \"%s\" to \"%s\" -- ", src, dst);
   }

   if (update && dff && ::CompareFileTime(&dff->ftLastWriteTime,&ff->ftLastWriteTime) >= 0)
   {
      if ( verbose )
      {
         Con::o("skip\r\n");
      }
      return 0;
   }

   if ( interactive && dff )
   {
      if ( verbose ) Con::n();
      Con::o('"'); Con::o(dst);
      s_confirm = (char)Con::confirm("\" exists. Overwrite it", s_confirm);
      if ( s_confirm != 'y' && s_confirm != 'Y' )
      {
         return 0; // not confirmed
      }
   }

   if ( force && dff )
   {
      if (0 != (dff->dwFileAttributes & FILE_ATTRIBUTE_READONLY) )
         ::SetFileAttributesW(dst, dff->dwFileAttributes & ~FILE_ATTRIBUTE_READONLY);
      if (force > 1 && 0 == (dff->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
         ::DeleteFileW(dst);
   }

   BOOL  res;
   DWORD flg;
   bool  sh = false;
   bool  have_sa = preserve && get_security_attr(src);
   if ( op[0] == 'c') {
      if ( 0 == (ff->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ) {
        flg = COPY_FILE_ALLOW_DECRYPTED_DESTINATION;
        res = ::CopyFileExW( src, dst, NULL, NULL, NULL, flg);
      }
      else {
         src[::lstrlenW(src) + 1] = L'\0';

         if ( dff ) // avoid double 'copy_to_directory' interpretation
         {
            WCHAR *pw = dst + ::lstrlenW(dst) - 1;
            while ( pw >= dst ) {
               if ( *pw == L'\\' || *pw == L'/' ) { *pw = L'\0'; break; } else { --pw; }
            }
         }
         dst[::lstrlenW(dst) + 1] = L'\0';

         sh = true;
         SHFILEOPSTRUCTW sho;

         sho.wFunc  = FO_COPY;
         sho.pFrom  = src;
         sho.pTo    = dst;
         sho.fFlags = FOF_SILENT | FOF_NOERRORUI | FOF_NOCONFIRMATION | FOF_NOCONFIRMMKDIR;
         if ( !recursive ) sho.fFlags |= FOF_NORECURSION;

         sho.hwnd                  = NULL;
         sho.lpszProgressTitle     = NULL;
         sho.hNameMappings         = NULL;
         sho.fAnyOperationsAborted = FALSE;


         int rr = ::SHFileOperation(&sho);
         res = ( rr == 0 );
         ::SetLastError(0);
      }
   }
   else {
      flg = MOVEFILE_COPY_ALLOWED;
      if ( 0 == (ff->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) )
         flg |= MOVEFILE_REPLACE_EXISTING;

      res = ::MoveFileExW(src, dst, flg);
   }

   if ( verbose )
   {
      Con::o(res ? "ok" : "failed"); Con::n();
   }

   int rc = 0;
   if ( !res )
   {
      const char *msg = ( op[0] == 'c' ) ? "Can't copy to" : "Can't move to";
      rc = err_ret(-22, msg, dst);
   }

   if ( res && preserve && !sh )
   {
      if ( op[0] == 'c' ) {
         HANDLE fh = ::CreateFileW(dst, GENERIC_WRITE,0,NULL,OPEN_EXISTING,0,NULL);
         if ( INVALID_HANDLE_VALUE != fh )
         {
            FILETIME ft;
            ::GetSystemTimeAsFileTime(&ft);
            ::SetFileTime(fh, NULL, NULL, &ft);
            ::CloseHandle(fh);
         }
      }
      if ( have_sa ) {
         set_security_attr(dst);
      }
   }

   return rc;
}

//=============================================================================

int cp_mv(
   const char *op,
   WCHAR **args, int na, int verbose,
   bool preserve, int force, bool interactive, bool update, bool recursive )
{
   int i, rc;
   WCHAR srcn[512], dstn[512], *dst, *pw, last[2];

   s_confirm = 'y';

   if ( na < 2 )
      return Opt::opt_err(Opt::Err_Missed_Param);
   
   if (Opt::have_wildcard(dst = args[na-1]))
   { ::SetLastError(ERROR_INVALID_NAME); return err_ret(-20,"Invalid target name:",dst); }

   bool dst_is_dir;
   last[0] = Opt::last_slash_or_colon(dst); last[1] = L'\0';
   if (last[0]) {
      dst_is_dir = true; dst[::lstrlenW(dst)-1] = L'\0';
   }
   else {
      dst_is_dir = Win::exists(dst, FILE_ATTRIBUTE_DIRECTORY);
      if (dst_is_dir) last[0] = L'\\';
   }

   if ( !dst_is_dir && na > 2 )
      return Opt::opt_err(Opt::Err_TooMany_Params);
   
   for ( rc = i = 0; i < na - 1 && rc == 0; ++i )
   {
      pw = args[i];
      if (Opt::end_with_slash(pw)) {
         if (Opt::is_root(pw)) {
            ::SetLastError(ERROR_INVALID_NAME); return err_ret(-20,"Invalid source name:",pw);
         }
         Opt::norm_slash(pw, pw, ::lstrlen(pw)+1, -1);
      }

      WIN32_FIND_DATAW ff;
      HANDLE hFind = ::FindFirstFileW(args[i], &ff);
      if ( INVALID_HANDLE_VALUE == hFind )
      {
         ::SetLastError(0);
         if (force && Opt::have_wildcard(args[i])) {
            if (verbose) { 
               Con::o("Source file(s) does not exist: "); Con::o(args[i]); Con::n();
            }
            continue;
         }
         else
            return err_ret(-21, "Source file(s) does not exist:", args[i]);
      }
      do 
      {
         if (Opt::is_dot(ff.cFileName))
            continue;

         if ( dst_is_dir )
         {
            ::lstrcpyW(dstn, dst);
            ::lstrcatW(dstn, last);
            ::lstrcatW(dstn,ff.cFileName);
         }
         else ::lstrcpyW(dstn, args[na-1]);

          
         ::lstrcpyW(srcn, args[i]);
         pw = srcn + ::lstrlenW(srcn);
         while ( pw > srcn && pw[-1] != L'\\' && pw[-1] != L'/' && pw[-1] != L':') --pw;
         ::lstrcpyW(pw, ff.cFileName);

         rc = cp_mv(
            op, srcn,dstn,&ff, verbose,preserve,force,interactive,update,recursive
         );
         if ( rc || !dst_is_dir )
            break;
         
      } while ( ::FindNextFileW(hFind, &ff) );

      ::FindClose(hFind);
   }

   return rc;
}

//=============================================================================
